﻿using JESAI.ClamAV.NetScannerCore.Enums;
using JESAI.ClamAV.NetScannerCore.Exceptions;
using JESAI.ClamAV.NetScannerCore.TcpClients;
using JESAI.ClamAV.NetScannerCore.TcpClients.Builder;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.ClamAV.NetScannerCore.Command
{
    public class CommandExecutor: ICommandExecutor
    {
        private readonly ICommandBuilder CommandBuilder;
        private readonly IScannerTcpClientBuilder ScannerTcpClientBuilder;
        public IScannerTCPClient SocketClient { get; private set; }
        public CommandExecutor(IScannerTcpClientBuilder scannerTcpClientBuilder, ICommandBuilder commandBuilder)
        {
            ScannerTcpClientBuilder = scannerTcpClientBuilder;
           
            CommandBuilder = commandBuilder;
        }       

        public async Task<string> ExecuteAsync(ClamAVCommand command, Func<NetworkStream, Task> additionalCommand = null)
        {
#if DEBUG
            var stopWatch = System.Diagnostics.Stopwatch.StartNew();
#endif
            string result;
            try
            {
                this.SocketClient = await ScannerTcpClientBuilder.BuildAsync();
                using (var stream = this.SocketClient.GetStream())
                {
                    var commandBytes = CommandBuilder.SetCommand(command).CreateCommand().CommantToBytes().Build();
                    await stream.WriteAsync(commandBytes, 0, commandBytes.Length);

                    if (additionalCommand != null)
                    {
                        await additionalCommand(stream);
                    }

                    using (var reader = new StreamReader(stream))
                    {
                        result = await reader.ReadToEndAsync();

                        if (!string.IsNullOrEmpty(result))
                        {
                            result = result.TrimEnd('\0');
                        }
                    }
                }
#if DEBUG
                stopWatch.Stop();
                System.Diagnostics.Debug.WriteLine("Command {0} took: {1}", command, stopWatch.Elapsed);
#endif
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (this.SocketClient.Connected)
                {
                    this.SocketClient.Close();
                }
            }
            return result;
        }

        public  string Execute(ClamAVCommand command, Func<NetworkStream,string> additionalCommand = null)
        {
#if DEBUG
            var stopWatch = System.Diagnostics.Stopwatch.StartNew();
#endif
            string result;
            try
            {
                this.SocketClient = ScannerTcpClientBuilder.Build();
                using (var stream = this.SocketClient.GetStream())
                {
                    var commandBytes = CommandBuilder.SetCommand(command).CreateCommand().CommantToBytes().Build();
                    stream.Write(commandBytes, 0, commandBytes.Length);

                    if (additionalCommand != null)
                    {
                        additionalCommand(stream);
                    }

                    using (var reader = new StreamReader(stream))
                    {
                        result = reader.ReadToEnd();

                        if (!string.IsNullOrEmpty(result))
                        {
                            result = result.TrimEnd('\0');
                        }
                    }
                }
#if DEBUG
                stopWatch.Stop();
                System.Diagnostics.Debug.WriteLine("Command {0} took: {1}", command, stopWatch.Elapsed);
#endif
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (this.SocketClient.Connected)
                {
                    this.SocketClient.Close();
                }
            }
            return result;
        }       

        public async Task<string> ExecuteAsync(ClamAVCommand command, CancellationToken cancellationToken, Func<NetworkStream, CancellationToken, Task> additionalCommand = null)
        {
#if DEBUG
            var stopWatch = System.Diagnostics.Stopwatch.StartNew();
#endif
            string result;
            try
            {
                this.SocketClient = await ScannerTcpClientBuilder.BuildAsync();
                using (var stream = this.SocketClient.GetStream())
                {
                    var commandBytes = CommandBuilder.SetCommand(command).CreateCommand().CommantToBytes().Build();
                    await stream.WriteAsync(commandBytes, 0, commandBytes.Length, cancellationToken);

                    if (additionalCommand != null)
                    {
                        await additionalCommand(stream, cancellationToken);
                    }

                    using (var reader = new StreamReader(stream))
                    {
                        result = await reader.ReadToEndAsync();

                        if (!string.IsNullOrEmpty(result))
                        {
                            result = result.TrimEnd('\0');
                        }
                    }
                }
#if DEBUG
                stopWatch.Stop();
                System.Diagnostics.Debug.WriteLine("Command {0} took: {1}", command, stopWatch.Elapsed);
#endif
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (this.SocketClient.Connected)
                {
                    this.SocketClient.Close();
                }
            }
            return result;
        }

        public string Execute(string command, Func<NetworkStream, string> additionalCommand = null)
        {
#if DEBUG
            var stopWatch = System.Diagnostics.Stopwatch.StartNew();
#endif
            string result;
            try
            {
                this.SocketClient = ScannerTcpClientBuilder.Build();
                var commandString = $"{command}".ToUpperInvariant();
                using (var stream = this.SocketClient.GetStream())
                {
                    var commandBytes = Encoding.UTF8.GetBytes($"z{commandString}\0");
                    stream.Write(commandBytes, 0, commandBytes.Length);

                    if (additionalCommand != null)
                    {
                        additionalCommand(stream);
                    }

                    using (var reader = new StreamReader(stream))
                    {
                        result = reader.ReadToEnd();

                        if (!string.IsNullOrEmpty(result))
                        {
                            result = result.TrimEnd('\0');
                        }
                    }
                }
#if DEBUG
                stopWatch.Stop();
                System.Diagnostics.Debug.WriteLine("Command {0} took: {1}", command, stopWatch.Elapsed);
#endif
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (this.SocketClient.Connected)
                {
                    this.SocketClient.Close();
                }
            }
            return result;
        }

        public async Task<string> ExecuteAsync(string command, Func<NetworkStream, Task> additionalCommand = null)
        {
#if DEBUG
            var stopWatch = System.Diagnostics.Stopwatch.StartNew();
#endif
            string result;
            try
            {
                this.SocketClient = await ScannerTcpClientBuilder.BuildAsync();
                var commandString = $"{command}".ToUpperInvariant();
                using (var stream = this.SocketClient.GetStream())
                {
                    var commandBytes = Encoding.UTF8.GetBytes($"z{commandString}\0"); 
                    await stream.WriteAsync(commandBytes, 0, commandBytes.Length);

                    if (additionalCommand != null)
                    {
                        await additionalCommand(stream);
                    }

                    using (var reader = new StreamReader(stream))
                    {
                        result = await reader.ReadToEndAsync();

                        if (!string.IsNullOrEmpty(result))
                        {
                            result = result.TrimEnd('\0');
                        }
                    }
                }
#if DEBUG
                stopWatch.Stop();
                System.Diagnostics.Debug.WriteLine("Command {0} took: {1}", command, stopWatch.Elapsed);
#endif
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (this.SocketClient.Connected)
                {
                    this.SocketClient.Close();
                }
            }
            return result;
        }

        public async Task<string> ExecuteAsync(string command, CancellationToken cancellationToken, Func<NetworkStream, CancellationToken, Task> additionalCommand = null)
        {
#if DEBUG
            var stopWatch = System.Diagnostics.Stopwatch.StartNew();
#endif
            string result;
            try
            {
                this.SocketClient = await ScannerTcpClientBuilder.BuildAsync();
                var commandString = $"{command}".ToUpperInvariant();
                using (var stream = this.SocketClient.GetStream())
                {
                    var commandBytes = Encoding.UTF8.GetBytes($"z{commandString}\0");
                    await stream.WriteAsync(commandBytes, 0, commandBytes.Length, cancellationToken);

                    if (additionalCommand != null)
                    {
                        await additionalCommand(stream, cancellationToken);
                    }

                    using (var reader = new StreamReader(stream))
                    {
                        result = await reader.ReadToEndAsync();

                        if (!string.IsNullOrEmpty(result))
                        {
                            result = result.TrimEnd('\0');
                        }
                    }
                }
#if DEBUG
                stopWatch.Stop();
                System.Diagnostics.Debug.WriteLine("Command {0} took: {1}", command, stopWatch.Elapsed);
#endif
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (this.SocketClient.Connected)
                {
                    this.SocketClient.Close();
                }
            }
            return result;
        }
    }
}